package com.suncky.frame.utils.image;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.net.Uri;
import android.os.AsyncTask;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;

import androidx.annotation.NonNull;

import com.suncky.frame.AppConfig;
import com.suncky.frame.utils.LogUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 图片水印工具
 */
public class ImageWaterMaker {
    private final Context context;
    private String targetDir;
    private Bitmap.CompressFormat outputFormat = null;//输出格式
    private List<String> waterMarkerTexts;//水印文字列表
    private Bitmap waterMarkerImage;//水印图片
    private int waterMarkerSpace;//相邻文字水印间距
    private float textSize;//文字像素大小, 传小于0则使用默认大小
    private float lineSpaceMulti = 1.0f;//一条文字水印中多行之间的行距倍数,默认1倍行距
    private int margin;//水印边缘预留空间宽度, 小于0按0处理
    private int gravity;//文字水印在图片上的位置
    private int positionX = 0, positionY = 0;//绘制水印的起始位置,当positionX和positionY不全为0时,gravity无效
    private float rotateDegree;//每条水印旋转的角度
    private int alpha;//水印透明度(0-255)
    private final List<InputStreamProvider> inputStreamProviders;
    private final MainThreadExecutor mainThreadExecutor;
    private boolean isError = false;//是否出现异常
    private boolean isCanceled = false;//压缩操作是否已被取消
    private WaterMakerCallback callback;

    public static ImageWaterMaker with(Context context) {
        return new ImageWaterMaker(context);
    }

    private ImageWaterMaker(Context context) {
        this.context = context;
        inputStreamProviders = new ArrayList<>();
        mainThreadExecutor = new MainThreadExecutor(context);
    }

    /**
     * 设置水印文字列表
     * @param waterMarkerTexts 要添加的水印文字列表
     * @return
     */
    public ImageWaterMaker waterMarkerText(List<String> waterMarkerTexts) {
        this.waterMarkerTexts = new ArrayList<>(waterMarkerTexts);
        return this;
    }

    /**
     * 设置水印图片
     * @param waterMarkerImage 要添加的水印图片.
     * @return
     */
    public ImageWaterMaker waterMarkerImage(Bitmap waterMarkerImage) {
        this.waterMarkerImage = waterMarkerImage;
        return this;
    }

    /**
     * 设置相邻文字水印之间的间距
     * @param waterMarkerSpace 文字水印之间的间距(像素)
     * @return
     */
    public ImageWaterMaker space(int waterMarkerSpace) {
        this.waterMarkerSpace = waterMarkerSpace;
        return this;
    }

    /**
     * 设置水印文字大小(像素)
     * @param textSize
     * @return
     */
    public ImageWaterMaker textSize(float textSize) {
        this.textSize = textSize;
        return this;
    }

    /**
     * 一条文字水印中换行文字之间的行距倍数,默认1.0倍行距
     * @param lineSpaceMulti 行距倍数
     * @return
     */
    public ImageWaterMaker textLineSpaceMulti(float lineSpaceMulti) {
        this.lineSpaceMulti = lineSpaceMulti;
        return this;
    }

    /**
     * 设置水印边缘预留空间宽度, 小于0按0处理
     * @param margin 水印边缘预留宽度
     * @return
     */
    public ImageWaterMaker margin(int margin) {
        this.margin = margin;
        return this;
    }

    /**
     * 设置每条水印的旋转角度
     * @param rotateDegree 旋转角度
     * @return
     */
    public ImageWaterMaker rotateDegree(float rotateDegree) {
        this.rotateDegree = rotateDegree;
        return this;
    }

    /**
     * 设置水印的透明度
     * @param alpha 透明度(0-255)
     * @return
     */
    public ImageWaterMaker alpha(int alpha) {
        if (alpha < 0) {
            alpha = 0;
        }
        if (alpha > 255) {
            alpha = 255;
        }
        this.alpha = alpha;
        return this;
    }

    /**
     * 设置水印的位置
     * @param gravity 水印的位置, 取{@link Gravity}中的值, 注意：当{@link #position(int, int)}设置的值不全为0时, 此方法设置的值无效
     * @return
     */
    public ImageWaterMaker gravity(int gravity) {
        this.gravity = gravity;
        return this;
    }

    /**
     * 在图片上添加水印的起始位置(图片左上角为 0,0), 如果参数positionX和positionY不全为0, 则在该位置上添加水印, {@link #gravity(int)}的设置无效
     * @param positionX x轴方向上的位置
     * @param positionY y轴方向上的位置
     * @return
     */
    public ImageWaterMaker position(int positionX, int positionY) {
        this.positionX = positionX;
        this.positionY = positionY;
        return this;
    }

    /**
     * 压缩图片输出目录
     * @param targetDir 输出目录
     * @return
     */
    public ImageWaterMaker targetDir(String targetDir) {
        this.targetDir = targetDir;
        return this;
    }

    /**
     * 图片输出格式. 默认按原图片格式输出
     * @param outputFormat 输出格式
     * @return
     */
    public ImageWaterMaker outputFormat(Bitmap.CompressFormat outputFormat) {
        this.outputFormat = outputFormat;
        return this;
    }

    /**
     * 设置回调
     * @param callback
     * @return
     */
    public ImageWaterMaker callback(WaterMakerCallback callback) {
        this.callback = callback;
        return this;
    }

    /**
     * 原图片路径
     * @param sourcePaths 路径数组
     * @return
     */
    public ImageWaterMaker source(@NonNull String... sourcePaths) {
        for (String path : sourcePaths) {
            this.inputStreamProviders.add(new InputStreamProvider() {
                @Override
                public InputStream getInputStream() throws IOException {
                    return new FileInputStream(path);
                }

                @Override
                public String getPath() {
                    return path;
                }
            });
        }
        return this;
    }

    /**
     * 原图片路径
     * @param sourceUris 路径数组
     * @return
     */
    public ImageWaterMaker source(@NonNull Uri... sourceUris) {
        for (Uri uri : sourceUris) {
            this.inputStreamProviders.add(new InputStreamProvider() {
                @Override
                public InputStream getInputStream() throws IOException {
                    return context.getContentResolver().openInputStream(uri);
                }

                @Override
                public String getPath() {
                    return uri.getPath();
                }
            });
        }
        return this;
    }

    /**
     * 原图片路径
     * @param sourceUFiles 路径数组
     * @return
     */
    public ImageWaterMaker source(@NonNull File... sourceUFiles) {
        for (File file : sourceUFiles) {
            this.inputStreamProviders.add(new InputStreamProvider() {
                @Override
                public InputStream getInputStream() throws IOException {
                    return new FileInputStream(file);
                }

                @Override
                public String getPath() {
                    return file.getAbsolutePath();
                }
            });
        }
        return this;
    }

    /**
     * 原图片路径
     * @param sources 路径数组
     * @return
     */
    public <T> ImageWaterMaker source(@NonNull List<T> sources) {
        for (T src : sources) {
            if (src instanceof String) {
                source((String) src);
            } else if (src instanceof File) {
                source((File) src);
            } else if (src instanceof Uri) {
                source((Uri) src);
            } else {
                throw new IllegalArgumentException("Original data type exception, it must be String, File, Uri");
            }
        }
        return this;
    }

    /**
     * 取消压缩
     */
    public void cancel() {
        this.isCanceled = true;
    }

    /**
     * 是否被取消
     * @return
     */
    public boolean isCanceled() {
        return isCanceled;
    }



    /**
     * 开始添加水印
     */
    public void launch() {
        if (inputStreamProviders.size() == 0) {
            return;
        }
        if (positionX != 0 || positionY != 0) {
            //已指定位置的情况下,按左上方位进行处理
            gravity = Gravity.LEFT_TOP;
        }
        if (waterMarkerTexts != null && !waterMarkerTexts.isEmpty()
                && (gravity == Gravity.BOTTOM_CENTER || gravity == Gravity.LEFT_BOTTOM || gravity == Gravity.RIGHT_BOTTOM)) {
            //水印位置在底部时,从下往上依次绘制,因此需要调转文字列表顺序
            Collections.reverse(waterMarkerTexts);
        }
        File[] toFiles = new File[this.inputStreamProviders.size()];
        if (callback != null) {
            callback.onStart();

            LogUtils.i("=============================================================================================");
            LogUtils.i("WaterMarker start!");
            LogUtils.i("--------------------------------------------");
            long startMillis = System.currentTimeMillis();
            for (int i = 0; i < inputStreamProviders.size(); ++i) {
                int index = i;
                AsyncTask.SERIAL_EXECUTOR.execute(() -> {
                    if (isError || isCanceled) {
                        return;
                    }
                    try {
                        File result = waterMarker(index);
                        toFiles[index] = result;
                        if (!isError && !isCanceled && index == toFiles.length - 1 && callback != null) {
                            LogUtils.i("WaterMarker finish! takes " + (System.currentTimeMillis() - startMillis) + " milliseconds");
                            LogUtils.i("=============================================================================================");
                            waterMarkerImage = null;
                            mainThreadExecutor.execute(() -> {
                                callback.onSuccess(toFiles);
                            });
                        }
                    } catch (Exception e) {
                        isError = true;
                        waterMarkerImage = null;
                        if (callback != null) {
                            mainThreadExecutor.execute(() -> {
                                callback.onError(e, inputStreamProviders.get(index).getPath());
                            });
                        }
                    }
                });
            }
        }
    }

    /**
     * 添加水印
     * @param pathIndex 图片列表索引号
     * @return 添加水印后的图片文件
     * @throws IOException
     */
    private File waterMarker(int pathIndex) throws IOException {
        Bitmap srcBitmap;
        String mimeType = getMIMEType(pathIndex);
        InputStream is = inputStreamProviders.get(pathIndex).getInputStream();
        if ("image/jpeg".equals(mimeType)) {
            srcBitmap = rotateBitmap(is, pathIndex);
        } else {
            srcBitmap = BitmapFactory.decodeStream(is);
        }
        is.close();
        if (outputFormat == null) {
            if ("image/png".equals(mimeType)) {
                outputFormat = Bitmap.CompressFormat.PNG;
            } else {
                outputFormat = Bitmap.CompressFormat.JPEG;
            }
        }
        String postfix = ".jpg";
        if (outputFormat == Bitmap.CompressFormat.PNG) {
            postfix = ".png";
        } else if (outputFormat == Bitmap.CompressFormat.WEBP) {
            postfix = ".webp";
        }
        if (targetDir == null || targetDir.length() == 0) {
            targetDir = AppConfig.imagePath;
        }
        File toDirFile = new File(targetDir);
        if (!toDirFile.exists()) {
            toDirFile.mkdirs();
        }
        Bitmap markedBitmap = drawMaker(srcBitmap);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        markedBitmap.compress(outputFormat, 100, baos);

        String toPath = targetDir + File.separator + "aframe_marker_" + System.currentTimeMillis() + postfix;
        File destFile = new File(toPath);
        FileOutputStream out = new FileOutputStream(destFile);
        out.write(baos.toByteArray());
        out.flush();
        out.close();
        baos.close();
        srcBitmap.recycle();
        return destFile;
    }

    private Bitmap drawMaker(Bitmap srcBitmap) {
        if ((waterMarkerTexts == null || waterMarkerTexts.isEmpty()) && waterMarkerImage == null) {
            return srcBitmap;
        }
        int srcWidth = srcBitmap.getWidth();
        int srcHeight = srcBitmap.getHeight();
        //创建一个bitmap实例，作为输出图片
        Bitmap outBitmap = Bitmap.createBitmap(srcWidth, srcHeight, srcBitmap.getConfig());
        Canvas canvas = new Canvas(outBitmap);
        //将原图片绘制到canvas上
        canvas.drawBitmap(srcBitmap, 0, 0, null);
        drawBitmapMarker(canvas);
        drawTextMaker(canvas);
        return outBitmap;
    }

    /**
     * 在原图片上绘制文字水印
     * @param canvas 用于绘制水印的画布
     * @return 添加水印后的图片
     */
    private void drawTextMaker(Canvas canvas) {
        if (waterMarkerTexts == null || waterMarkerTexts.isEmpty()) {
            return;
        }
        canvas.save();
        int srcWidth = canvas.getWidth();
        int srcHeight = canvas.getHeight();
        //创建paint实例,用于绘制水印文字
        TextPaint markerPaint = new TextPaint();
        markerPaint.setColor(Color.WHITE);
        float size = textSize > 0 ? textSize : (srcWidth / 25f);
        markerPaint.setTextSize(size);
        markerPaint.setAntiAlias(true);
        markerPaint.setAlpha(alpha);
        int markerSpace = Math.max(waterMarkerSpace, 0);
        int markerMargin = Math.max(margin, 0);
        int totalHeight = 0;//水印总高度
        int maxWidth = 0;//水印最大宽度
        List<StaticLayout> staticLayouts = new ArrayList<>();
        //筛选非空字符串创建对应的StaticLayout对象
        for (String marker : waterMarkerTexts) {
            if (TextUtils.isEmpty(marker)) {
                continue;
            }
            //StaticLayout的宽度自适应内容
            int width = Math.min((int) markerPaint.measureText(marker), srcWidth - markerMargin * 2);
            Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;//默认左对齐
            if (gravity == Gravity.RIGHT_TOP || gravity == Gravity.RIGHT_BOTTOM || gravity == Gravity.RIGHT_CENTER) {
                //如果水印加在右侧,靠右对齐
                alignment = Layout.Alignment.ALIGN_OPPOSITE;
            }
            //StaticLayout可处理文字绘制的换行、行距等问题
            StaticLayout staticLayout = new StaticLayout(marker, markerPaint, width, alignment, lineSpaceMulti, 0.0f, true);
            maxWidth = Math.max(maxWidth, staticLayout.getWidth());
            totalHeight += staticLayout.getHeight();
            staticLayouts.add(staticLayout);
        }
        int translateY = 0;//画笔移动距离Y
        int lastMarkerHeight = 0;//记录上一个staticLayout的高度
        //依次绘制staticLayout对象中的内容
        for (int index = 0; index < staticLayouts.size(); ++index) {
            StaticLayout staticLayout = staticLayouts.get(index);
            int markerWidth = staticLayout.getWidth();
            int markerHeight = staticLayout.getHeight();
            //计算在原图片上绘制文字的位置
            int translateX = 0;//画笔移动距离X
            switch (gravity) {
                case Gravity.LEFT_TOP://左上需考虑指定位置
                    translateX = markerMargin + positionX;
                    if (index == 0) {
                        translateY = markerMargin + positionY;
                    } else {
                        translateY += (lastMarkerHeight + markerSpace);
                    }
                    break;
                case Gravity.RIGHT_TOP:
                    translateX = srcWidth - markerWidth - markerMargin;
                    if (index == 0) {
                        translateY = markerMargin;
                    } else {
                        translateY += (lastMarkerHeight + markerSpace);
                    }
                    break;
                case Gravity.LEFT_BOTTOM:
                    translateX = markerMargin;
                    if (index == 0) {
                        translateY = srcHeight - markerHeight - markerMargin;
                    } else {
                        translateY -= (markerHeight + markerSpace);
                    }
                    break;
                case Gravity.RIGHT_BOTTOM:
                    translateX = srcWidth - markerWidth - markerMargin;
                    if (index == 0) {
                        translateY = srcHeight - markerHeight - markerMargin;
                    } else {
                        translateY -= (markerHeight + markerSpace);
                    }
                    break;
                case Gravity.TOP_CENTER:
                    translateX = (srcWidth - markerWidth) / 2;
                    if (index == 0) {
                        translateY = markerMargin;
                    } else {
                        translateY += (lastMarkerHeight + markerSpace);
                    }
                    break;
                case Gravity.BOTTOM_CENTER:
                    translateX = (srcWidth - markerWidth) / 2;
                    if (index == 0) {
                        translateY = srcHeight - markerHeight - markerMargin;
                    } else {
                        translateY -= (markerHeight + markerSpace);
                    }
                    break;
                case Gravity.LEFT_CENTER:
                    translateX = markerMargin;
                    if (index == 0) {
                        translateY = (srcHeight - totalHeight) / 2;
                    } else {
                        translateY += (lastMarkerHeight + markerSpace);
                    }
                    break;
                case Gravity.RIGHT_CENTER:
                    translateX = srcWidth - markerWidth - markerMargin;
                    if (index == 0) {
                        translateY = (srcHeight - totalHeight) / 2;
                    } else {
                        translateY += (lastMarkerHeight + markerSpace);
                    }
                    break;
                case Gravity.CENTER:
                default:
                    translateX = (srcWidth - markerWidth) / 2;
                    if (index == 0) {
                        translateY = (srcHeight - totalHeight) / 2;
                    } else {
                        translateY += (lastMarkerHeight + markerSpace);
                    }
                    break;
            }
            PointF rotatePoint = getRotatePivotPoint(markerWidth, markerHeight);
            //指定绘制水印的位置
            canvas.translate(translateX, translateY);
            //以旋转轴为中心旋转
            canvas.rotate(rotateDegree, rotatePoint.x, rotatePoint.y);
            //staticLayout中的内容绘制到canvas上
            staticLayout.draw(canvas);
            //复位
            canvas.rotate(-rotateDegree, rotatePoint.x, rotatePoint.y);
            canvas.translate(-translateX, -translateY);
            lastMarkerHeight = markerHeight;
        }
        canvas.restore();
    }

    /**
     * 绘制图片水印
     * @param canvas 绘制水印的画布
     * @return
     */
    private void drawBitmapMarker(Canvas canvas) {
        if (waterMarkerImage == null) {
            return;
        }
        canvas.save();
        int srcWidth = canvas.getWidth();
        int srcHeight = canvas.getHeight();
        int markerMargin = Math.max(margin, 0);
        int markerWidth = waterMarkerImage.getWidth();
        int markerHeight = waterMarkerImage.getHeight();
        int translateX = 0;//画笔移动距离-X
        int translateY = 0;//画笔移动距离-Y
        switch (gravity) {
            case Gravity.LEFT_TOP:
                translateX = markerMargin + positionX;
                translateY = markerMargin + positionY;
                break;
            case Gravity.RIGHT_TOP:
                translateX = srcWidth - markerWidth - markerMargin;
                translateY = markerMargin;
                break;
            case Gravity.LEFT_BOTTOM:
                translateX = markerMargin;
                translateY = srcHeight - markerHeight - markerMargin;
                break;
            case Gravity.RIGHT_BOTTOM:
                translateX = srcWidth - markerWidth - markerMargin;
                translateY = srcHeight - markerHeight - markerMargin;
                break;
            case Gravity.TOP_CENTER:
                translateX = (srcWidth - markerWidth) / 2;
                translateY = markerMargin;
                break;
            case Gravity.BOTTOM_CENTER:
                translateX = (srcWidth - markerWidth) / 2;
                translateY = srcHeight - markerHeight - markerMargin;
                break;
            case Gravity.LEFT_CENTER:
                translateX = markerMargin;
                translateY = (srcHeight - markerHeight) / 2;
                break;
            case Gravity.RIGHT_CENTER:
                translateX = srcWidth - markerWidth - markerMargin;
                translateY = (srcHeight - markerHeight) / 2;
                break;
            case Gravity.CENTER:
            default:
                translateX = (srcWidth - markerWidth) / 2;
                translateY = (srcHeight - markerHeight) / 2;
                break;
        }
        PointF rotatePoint = getRotatePivotPoint(markerWidth, markerHeight);
        //指定绘制水印的位置
        canvas.translate(translateX, translateY);
        //以旋转轴为中心旋转
        canvas.rotate(rotateDegree, rotatePoint.x, rotatePoint.y);

        //绘制水印图片
        Paint markerPaint = new Paint();
        markerPaint.setAntiAlias(true);
        markerPaint.setAlpha(alpha);
        canvas.drawBitmap(waterMarkerImage, 0, 0, markerPaint);

        //复位
        canvas.rotate(-rotateDegree, rotatePoint.x, rotatePoint.y);
        canvas.translate(-translateX, -translateY);
        canvas.restore();
    }

    /**
     * 计算旋转轴心位置
     * @param markerWidth 水印宽度
     * @param markerHeight 水印高度
     * @return
     */
    private PointF getRotatePivotPoint(int markerWidth, int markerHeight) {
        int rotateX = 0;//旋转轴心距离-X
        int rotateY = 0;//旋转轴心距离-Y
        // 计算角度旋转轴心位置
        switch (gravity) {
            //水印靠左,以水印左侧为轴心
            case Gravity.LEFT_TOP:
            case Gravity.LEFT_CENTER:
            case Gravity.LEFT_BOTTOM:
                rotateX = 0;
                if (rotateDegree > 0) {
                    //顺时针旋转,以水印左下角为轴心
                    rotateY = markerHeight;
                } else {
                    //逆时针旋转,以水印左上角为轴心
                    rotateY = 0;
                }
                break;
            //水印靠右,以水印右侧为轴心
            case Gravity.RIGHT_TOP:
            case Gravity.RIGHT_CENTER:
            case Gravity.RIGHT_BOTTOM:
                rotateX = markerWidth;
                if (rotateDegree > 0) {
                    //顺时针旋转,以水印右上角为轴心
                    rotateY = 0;
                } else {
                    //逆时针旋转,以水印右下角为轴心
                    rotateY = markerHeight;
                }
                break;
            //水印水平居中,以水印的中心点为轴心
            case Gravity.TOP_CENTER:
            case Gravity.BOTTOM_CENTER:
            case Gravity.CENTER:
                rotateX = markerWidth / 2;
                rotateY = markerHeight / 2;
                break;
        }
        return new PointF(rotateX, rotateY);
    }

    //纠正图片角度
    private Bitmap rotateBitmap(InputStream is, int pathIndex) throws IOException {
        int degree;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            InputStream isAngle = inputStreamProviders.get(pathIndex).getInputStream();
            degree = Utils.getAngle(isAngle);
            isAngle.close();
        } else {
            degree = Utils.getAngle(inputStreamProviders.get(pathIndex).getPath());
        }
        Bitmap srcBitmap = BitmapFactory.decodeStream(is);
        if (degree != 0) {
            srcBitmap = Utils.getRotateBitmap(srcBitmap, degree);
        }
        return srcBitmap;
    }

    //获取图片MIME类型
    private String getMIMEType(int pathIndex) throws IOException {
        InputStream is = inputStreamProviders.get(pathIndex).getInputStream();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is,null, options);
        is.close();
        return options.outMimeType;
    }

    public interface WaterMakerCallback{
        void onStart();

        void onSuccess(@NonNull File... results);

        void onError(Throwable throwable, String... path);
    }

    public static class Gravity{
        /**左上**/
        public static final int LEFT_TOP = 1;
        /**左中**/
        public static final int LEFT_CENTER = 2;
        /**左下**/
        public static final int LEFT_BOTTOM = 3;
        /**右上**/
        public static final int RIGHT_TOP = 4;
        /**右中**/
        public static final int RIGHT_CENTER = 5;
        /**右下**/
        public static final int RIGHT_BOTTOM = 6;
        /**上中**/
        public static final int TOP_CENTER = 7;
        /**下中**/
        public static final int BOTTOM_CENTER = 8;
        /**正中**/
        public static final int CENTER = 9;
    }
}
